//===--- Explosion.h - Exploded R-Value Representation ----------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// A storage structure for holding an exploded r-value.  An exploded
// r-value has been separated into individual components.  Only types
// with non-resilient structure may be exploded.
//
// The standard use for explosions is for argument-passing.
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_IRGEN_INTERNAL_EXPLOSION_H
#define POLARPHP_IRGEN_INTERNAL_EXPLOSION_H

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "polarphp/irgen/internal/IRGen.h"
#include "polarphp/irgen/internal/IRGenFunction.h"

namespace polar::irgen {

/// The representation for an explosion is just a list of raw LLVM
/// values.  The meaning of these values is imposed externally by the
/// type infos, except that it is expected that they will be passed
/// as arguments in exactly this way.
class Explosion {
   unsigned NextValue;
   SmallVector<llvm::Value*, 8> Values;

public:
   Explosion() : NextValue(0) {}

   // We want to be a move-only type.
   Explosion(const Explosion &) = delete;
   Explosion &operator=(const Explosion &) = delete;
   Explosion(Explosion &&other) : NextValue(0) {
      // Do an uninitialized copy of the non-consumed elements.
      Values.reserve(other.size());
      Values.set_size(other.size());
      std::uninitialized_copy(other.begin(), other.end(), Values.begin());

      // Remove everything from the other explosion.
      other.reset();
   }

   Explosion &operator=(Explosion &&o) {
      assert(empty() && "explosion had values remaining when reassigned!");
      NextValue = o.NextValue;
      Values.swap(o.Values);
      o.reset();
      return *this;
   }

   ~Explosion() {
      assert(empty() && "explosion had values remaining when destroyed!");
   }

   bool empty() const {
      return NextValue == Values.size();
   }

   size_t size() const {
      return Values.size() - NextValue;
   }

   using iterator = SmallVector<llvm::Value *, 8>::iterator;
   iterator begin() { return Values.begin() + NextValue; }
   iterator end() { return Values.end(); }

   using const_iterator = SmallVector<llvm::Value *, 8>::const_iterator;
   const_iterator begin() const { return Values.begin() + NextValue; }
   const_iterator end() const { return Values.end(); }

   /// Add a value to the end of this exploded r-value.
   void add(llvm::Value *value) {
      assert(value && "adding null value to explosion");
      assert(NextValue == 0 && "adding to partially-claimed explosion?");
      Values.push_back(value);
   }

   void add(ArrayRef<llvm::Value*> values) {
#ifndef NDEBUG
      for (auto value : values)
         assert(value && "adding null value to explosion");
#endif
      assert(NextValue == 0 && "adding to partially-claimed explosion?");
      Values.append(values.begin(), values.end());
   }

   /// Return an array containing the given range of values.  The values
   /// are not claimed.
   ArrayRef<llvm::Value*> getRange(unsigned from, unsigned to) const {
      assert(from <= to);
      assert(to <= Values.size());
      return llvm::makeArrayRef(begin() + from, to - from);
   }

   /// Return an array containing all of the remaining values.  The values
   /// are not claimed.
   ArrayRef<llvm::Value *> getAll() {
      return llvm::makeArrayRef(begin(), Values.size() - NextValue);
   }

   /// Transfer ownership of the next N values to the given explosion.
   void transferInto(Explosion &other, unsigned n) {
      other.add(claim(n));
   }


   /// The next N values have been claimed in some indirect way (e.g.
   /// using getRange() and the like); just give up on them.
   void markClaimed(unsigned n) {
      assert(NextValue + n <= Values.size());
      NextValue += n;
   }

   /// Claim and return the next value in this explosion.
   llvm::Value *claimNext() {
      assert(NextValue < Values.size());
      return Values[NextValue++];
   }

   /// Claim and return the next N values in this explosion.
   ArrayRef<llvm::Value*> claim(unsigned n) {
      assert(NextValue + n <= Values.size());
      auto array = llvm::makeArrayRef(begin(), n);
      NextValue += n;
      return array;
   }

   /// Claim and return all the values in this explosion.
   ArrayRef<llvm::Value*> claimAll() {
      return claim(size());
   }

   // These are all kindof questionable.

   /// Without changing any state, take the last claimed value,
   /// if there is one.
   llvm::Value *getLastClaimed() {
      assert(NextValue > 0);
      return Values[NextValue-1];
   }

   /// Claim and remove the last item in the array.
   /// Unlike the normal 'claim' methods, the item is gone forever.
   llvm::Value *takeLast() {
      assert(!empty());
      auto result = Values.back();
      Values.pop_back();
      return result;
   }

   /// Reset this explosion.
   void reset() {
      NextValue = 0;
      Values.clear();
   }

   void print(llvm::raw_ostream &OS);
   void dump();
};

/// An explosion schema is essentially the type of an Explosion.
class ExplosionSchema {
public:

   /// The schema for one atom of the explosion.
   class Element {
      llvm::Type *Type;
      Alignment::int_type Align;
      Element() = default;
   public:
      static Element forScalar(llvm::Type *type) {
         Element e;
         e.Type = type;
         e.Align = 0;
         return e;
      }

      static Element forAggregate(llvm::Type *type, Alignment align) {
         assert(align.getValue() != 0 && "alignment with zero value!");
         Element e;
         e.Type = type;
         e.Align = align.getValue();
         return e;
      }

      bool isScalar() const { return Align == 0; }
      llvm::Type *getScalarType() const { assert(isScalar()); return Type; }

      bool isAggregate() const { return !isScalar(); }
      llvm::Type *getAggregateType() const {
         assert(isAggregate());
         return Type;
      }
      Alignment getAggregateAlignment() const {
         assert(isAggregate());
         return Alignment(Align);
      }
   };

private:
   SmallVector<Element, 8> Elements;
   bool ContainsAggregate;

public:
   ExplosionSchema() : ContainsAggregate(false) {}

   /// Return the number of elements in this schema.
   unsigned size() const { return Elements.size(); }
   bool empty() const { return Elements.empty(); }

   /// Does this schema contain an aggregate element?
   bool containsAggregate() const { return ContainsAggregate; }

   /// Does this schema consist solely of one aggregate element?
   bool isSingleAggregate() const {
      return size() == 1 && containsAggregate();
   }

   const Element &operator[](size_t index) const {
      return Elements[index];
   }

   using iterator = SmallVectorImpl<Element>::iterator;
   using const_iterator = SmallVectorImpl<Element>::const_iterator;

   iterator begin() { return Elements.begin(); }
   iterator end() { return Elements.end(); }
   const_iterator begin() const { return Elements.begin(); }
   const_iterator end() const { return Elements.end(); }

   void add(Element e) {
      Elements.push_back(e);
      ContainsAggregate |= e.isAggregate();
   }

   /// Produce the correct type for a direct return of this schema,
   /// which is assumed to contain only scalars.  This is defined as:
   ///   - void, if the schema is empty;
   ///   - the element type, if the schema contains exactly one element;
   ///   - an anonymous struct type concatenating those types, otherwise.
   llvm::Type *getScalarResultType(IRGenModule &IGM) const;
};

} // end namespace polar::irgen

#endif // POLARPHP_IRGEN_INTERNAL_EXPLOSION_H
